﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using SimpleIR;
using System.Diagnostics.Contracts;

namespace RgnAnalyzer.Domain
{
	/// <summary>
	/// Support for memory location use and mod.
	/// </summary>
	internal struct UseModInfo : IEquatable<UseModInfo>, IComparable<UseModInfo>
	{
		internal enum Value : byte
		{
			UseAsInternal = 0x1,
			ModAsInternal = 0x2,
			UseAsExternal = 0x4,
			ModAsExternal = 0x8
		};

		/// <summary>
		/// Tag indicating if this is a use or a modification.
		/// </summary>
		private readonly Value m_tag;

		/// <summary>
		/// The line number of the use/mod.
		/// </summary>
		private readonly int m_line;

		/// <summary>
		/// The location (as an access path) that is used or modified.
		/// </summary>
		private readonly IRBasicAccessPath m_bap;

		internal UseModInfo(Value tag, int line, IRBasicAccessPath umloc)
		{
			this.m_tag = tag;
			this.m_line = line;
			this.m_bap = umloc;
		}

		public override string ToString()
		{
			string tags;
			if(this.m_tag == Value.UseAsInternal)
				tags = "UI";
			else if(this.m_tag == Value.UseAsExternal)
				tags = "UE";
			else if(this.m_tag == Value.ModAsInternal)
				tags = "MI";
			else
				tags = "ME";

			string ltt = this.m_bap.ToString();

			return "[" + tags + "@" + this.m_line.ToString() + " on " + ltt + "]";
		}

		internal static void MarkUMIAtObjectAllocation(IRClassType alloct, List<UseModInfo> uml, int line)
		{
			foreach(IRBasicAccessPath bap in alloct.TypeDescription.AllTerminalAccessPaths)
				UseModInfo.MarkUMI(uml, true, false, false, true, line, bap);
		}

		internal static void MarkUMIAtStructInitialization(IRStructType alloct, List<UseModInfo> uml, int line)
		{
			foreach(IRBasicAccessPath bap in alloct.TypeDescription.AllTerminalAccessPaths)
				UseModInfo.MarkUMI(uml, true, false, false, true, line, bap);
		}

		internal static void MarkUMI(List<UseModInfo> uml, bool isinternal, bool isexternal, bool isread, bool iswrite, int line, IRBasicAccessPath ltt)
		{
			if(isread)
			{
				if(isinternal)
				{
					UseModInfo umv = new UseModInfo(UseModInfo.Value.UseAsInternal, line, ltt);
					int ump = uml.BinarySearch(umv);
					if(ump < 0)
						uml.Insert(~ump, umv);
				}

				if(isexternal)
				{
					UseModInfo umv = new UseModInfo(UseModInfo.Value.UseAsExternal, line, ltt);
					int ump = uml.BinarySearch(umv);
					if(ump < 0)
						uml.Insert(~ump, umv);
				}
			}

			if(iswrite)
			{
				if(isinternal)
				{
					UseModInfo umv = new UseModInfo(UseModInfo.Value.ModAsInternal, line, ltt);
					int ump = uml.BinarySearch(umv);
					if(ump < 0)
						uml.Insert(~ump, umv);
				}

				if(isexternal)
				{
					UseModInfo umv = new UseModInfo(UseModInfo.Value.ModAsExternal, line, ltt);
					int ump = uml.BinarySearch(umv);
					if(ump < 0)
						uml.Insert(~ump, umv);
				}
			}
		}

		public static bool UseModListsEqual(List<UseModInfo> uml1, List<UseModInfo> uml2)
		{
			if(uml1.Count != uml2.Count)
				return false;

			for(int i = 0; i < uml1.Count; ++i)
			{
				if(!uml1[i].Equals(uml2[i]))
					return false;
			}

			return true;
		}

		public static void UseModListsMerge(List<UseModInfo> into, List<UseModInfo> from)
		{
			int osize = into.Count;

			for(int i = 0; i < from.Count; ++i)
			{
				int pos = into.BinarySearch(0, osize, from[i], null);
				if(pos < 0)
					into.Add(from[i]);
			}

			if(osize != into.Count)
				into.Sort();
		}

		/// <summary>
		/// Return true if this location has had a value assigned to the given access path
		/// </summary>
		public static bool HasLocationOfPtrOrRefTypeBeenModifiedInCall(IRType locationtype, List<UseModInfo> uml)
		{
			for(int i = 0; i < uml.Count; ++i)
			{
				if((uml[i].m_tag == Value.ModAsExternal) | (uml[i].m_tag == Value.ModAsInternal))
				{
					if(uml[i].m_bap.IsEmptyAccessPath && (locationtype is IRPtrType || locationtype is IRReferenceType))
						return true;
					else if(uml[i].m_bap.PathLast.FieldType is IRPtrType)
						return true;
					else
					{ ;}
				}
			}

			return false;
		}

		public static bool operator ==(UseModInfo um1, UseModInfo um2)
		{ return um1.Equals(um2); }

		public static bool operator !=(UseModInfo um1, UseModInfo um2)
		{ return !um1.Equals(um2); }

		public static bool operator <(UseModInfo um1, UseModInfo um2)
		{ return um1.CompareTo(um2) < 0; }

		public static bool operator >(UseModInfo um1, UseModInfo um2)
		{ return um1.CompareTo(um2) > 0; }

		public override int GetHashCode()
		{ return (((int)this.m_tag) ^ this.m_line ^ ((int)this.m_bap.GetHashCode())); }

		public override bool Equals(Object obj)
		{ return (obj is UseModInfo) && this.Equals((UseModInfo)obj); }

		public bool Equals(UseModInfo other)
		{ return (this.m_tag == other.m_tag) & (this.m_line == other.m_line) & (this.m_bap == other.m_bap); }

		public int CompareTo(UseModInfo other)
		{
			int tagv = ((int)this.m_tag) - ((int)other.m_tag);
			if(tagv != 0)
				return tagv;
			else
			{
				int linev = (this.m_line - other.m_line);
				return (linev != 0) ? linev : this.m_bap.CompareTo(other.m_bap);
			}
		}

		/// <summary>
		/// Return tag indicating the type of the use/mod operation.
		/// </summary>
		public Value UMTag
		{ get { return this.m_tag; } }

		/// <summary>
		/// Return the line that this use/mod occoured on.
		/// </summary>
		public int UMLine
		{ get { return this.m_line; } }

		/// <summary>
		/// The type of the location that this use/mod occoured.
		/// </summary>
		public IRBasicAccessPath UMAccessPath
		{ get { return this.m_bap; } }
	}
}
